//The MIT License
//
//Copyright (c) 2009 nodchip
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package tv.dyndns.kishibe.client.creation;

import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Strings.nullToEmpty;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import tv.dyndns.kishibe.client.Service;
import tv.dyndns.kishibe.client.UserData;
import tv.dyndns.kishibe.client.game.ProblemGenre;
import tv.dyndns.kishibe.client.game.ProblemType;
import tv.dyndns.kishibe.client.game.RandomFlag;
import tv.dyndns.kishibe.client.packet.PacketProblem;
import tv.dyndns.kishibe.client.util.StringUtils;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class WidgetProblemForm extends VerticalPanel implements ClickHandler, ChangeHandler {
	private static final Logger logger = Logger.getLogger(WidgetProblemForm.class.getName());
	private static final int MAX_NUMBER_OF_ANSWERS = 8;
	private static final int MAX_NUMBER_OF_CHOICES = 8;
	private final Label labelProblemNumber = new Label("新規問題を作成中");
	private final ListBox listBoxGenre = new ListBox();
	@VisibleForTesting
	final ListBox listBoxType = new ListBox();
	private final ListBox listBoxRandomFlag = new ListBox();
	private final TextArea textAreaSentence = new TextArea();
	@VisibleForTesting
	final TextBox[] textBoxAnswer = new TextBox[MAX_NUMBER_OF_ANSWERS];
	@VisibleForTesting
	final TextBox[] textBoxChoice = new TextBox[MAX_NUMBER_OF_CHOICES];
	private final RadioButton radioButtonNone = new RadioButton("external", "使用しない");
	private final RadioButton radioButtonImage = new RadioButton("external", "画像");
	private final RadioButton radioButtonYouTube = new RadioButton("external", "YouTube");
	private final TextBox textBoxExternalUrl = new TextBox();
	private final HorizontalPanel panelExternalUrl = new HorizontalPanel();
	private final TextBox textBoxCreator = new TextBox();
	private final TextBox textBoxNote = new TextBox();
	private final CreationUi creationUi;
	private final Label labelAnswerCounter = new Label("0/0");
	private final Button buttonResetAnswerCount = new Button("リセット", this);
	private final Label labelVoteCounter = new Label("0/0");
	private final Button buttonResetVoteCount = new Button("リセット", this);
	private final CheckBox checkBoxImageChoice = new CheckBox("画像選択肢");
	private final CheckBox checkBoxImageAnswer = new CheckBox("画像回答");
	private final Button buttonRemovePlayerAnswers = new Button("誤回答を削除する", this);
	private int problemId = -1;
	private final List<Button> buttonPolygonCreation = new ArrayList<Button>();
	private final VerticalPanel panelProblemFeedback = new VerticalPanel();
	private final Button buttonClearProblemFeedback = new Button("クリア", this);

	public WidgetProblemForm(CreationUi creationUi) {
		this.creationUi = creationUi;

		// 問題番号
		add(labelProblemNumber);

		Grid grid = new Grid(12, 2);
		grid.addStyleName("gridFrame");
		grid.addStyleName("gridFontNormal");

		// ジャンル
		for (ProblemGenre genre : ProblemGenre.values()) {
			String name = genre == ProblemGenre.Random ? "ジャンルを選んでください" : genre.toString();
			getListBoxGenre().addItem(name, Integer.toString(genre.getIndex()));
		}
		getListBoxGenre().setWidth("200px");
		grid.setText(0, 0, "ジャンル");
		grid.setWidget(0, 1, getListBoxGenre());

		// 出題形式
		for (ProblemType type : ProblemType.values()) {
			if (ProblemType.Random1.compareTo(type) <= 0) {
				break;
			}
			String item = type == ProblemType.Random ? "出題形式を選んでください" : type.toString();
			listBoxType.addItem(item, type.name());
		}
		listBoxType.setWidth("200px");
		listBoxType.addChangeHandler(this);
		grid.setText(1, 0, "出題形式");
		grid.setWidget(1, 1, listBoxType);

		// ランダムフラグ
		for (int i = 1; i <= 5; ++i) {
			String s = Integer.toString(i);
			listBoxRandomFlag.addItem(s, RandomFlag.values()[i].name());
		}
		listBoxRandomFlag.setSelectedIndex(4);
		listBoxRandomFlag.setWidth("100px");
		grid.setText(2, 0, "ランダムフラグ");
		grid.setWidget(2, 1, listBoxRandomFlag);

		// 問題文
		getTextAreaSentence().setCharacterWidth(60);
		getTextAreaSentence().setVisibleLines(5);
		grid.setText(3, 0, "問題文");
		grid.setWidget(3, 1, getTextAreaSentence());

		// 選択肢
		VerticalPanel choicePanel = new VerticalPanel();
		choicePanel.setStyleName("gridNoFrame");
		choicePanel.addStyleName("gridFontNormal");
		for (int i = 0; i < MAX_NUMBER_OF_CHOICES; ++i) {
			textBoxChoice[i] = new TextBox();
			textBoxChoice[i].setWidth("400px");
			choicePanel.add(textBoxChoice[i]);
		}
		choicePanel.add(checkBoxImageChoice);
		grid.setText(4, 0, "選択肢");
		grid.setWidget(4, 1, choicePanel);

		// 解答
		VerticalPanel answerPanel = new VerticalPanel();
		answerPanel.setStyleName("gridNoFrame");
		answerPanel.addStyleName("gridFontNormal");
		for (int i = 0; i < MAX_NUMBER_OF_ANSWERS; ++i) {
			HorizontalPanel panel = new HorizontalPanel();
			panel.setVerticalAlignment(ALIGN_MIDDLE);
			answerPanel.add(panel);

			textBoxAnswer[i] = new TextBox();
			textBoxAnswer[i].setWidth("400px");
			panel.add(textBoxAnswer[i]);

			Button button = new Button("領域作成", this);
			panel.add(button);
			buttonPolygonCreation.add(button);
		}
		answerPanel.add(checkBoxImageAnswer);
		grid.setText(5, 0, "解答");
		grid.setWidget(5, 1, answerPanel);

		// 外部コンテンツ
		grid.setText(6, 0, "外部コンテンツ");
		{
			VerticalPanel panel = new VerticalPanel();
			panel.setStyleName("gridNoFrame");
			panel.addStyleName("gridFontNormal");
			{
				radioButtonNone.setValue(true);
				radioButtonNone.addClickHandler(this);
				radioButtonImage.addClickHandler(this);
				radioButtonYouTube.addClickHandler(this);

				HorizontalPanel panel2 = new HorizontalPanel();
				panel2.add(radioButtonNone);
				panel2.add(radioButtonImage);
				panel2.add(radioButtonYouTube);
				panel.add(panel2);
			}
			{
				textBoxExternalUrl.setWidth("400px");

				panelExternalUrl.add(new Label("URL"));
				panelExternalUrl.add(textBoxExternalUrl);
				panel.add(panelExternalUrl);
			}
			grid.setWidget(6, 1, panel);
		}

		// 作成者
		getTextBoxCreator().setWidth("200px");
		getTextBoxCreator().setMaxLength(6);
		getTextBoxCreator().setText(UserData.get().getPlayerName());
		grid.setText(7, 0, "問題作成者");
		grid.setWidget(7, 1, getTextBoxCreator());

		// 回答数
		grid.setText(8, 0, "回答数");
		HorizontalPanel panelAnswerCount = new HorizontalPanel();
		panelAnswerCount.add(getLabelAnswerCounter());
		panelAnswerCount.add(buttonResetAnswerCount);
		panelAnswerCount.add(buttonRemovePlayerAnswers);
		buttonRemovePlayerAnswers.setVisible(false);
		grid.setWidget(8, 1, panelAnswerCount);

		// 回答数
		grid.setText(9, 0, "投票数");
		HorizontalPanel panelVoteCount = new HorizontalPanel();
		panelVoteCount.add(labelVoteCounter);
		panelVoteCount.add(buttonResetVoteCount);
		grid.setWidget(9, 1, panelVoteCount);

		// 問題ノート
		grid.setText(10, 0, "問題ノート");
		textBoxNote.setMaxLength(1024);
		textBoxNote.setWidth("400px");
		grid.setWidget(10, 1, textBoxNote);

		// 問題評価
		grid.setText(11, 0, "問題評価");
		grid.setWidget(11, 1, panelProblemFeedback);

		add(grid);

		updateForm();
	}

	/**
	 * 問題を設定する
	 * 
	 * @param problem
	 *            問題
	 * @param copy
	 *            問題をコピーして新しい問題を作成する場合は{@code true}、既存の問題を修正する場合は{@code false}
	 */
	public void setProblem(PacketProblem problem, boolean copy) {
		// TODO 範囲外の値をセットしようとしたときにJavaとブラウザで挙動が違うのを報告する
		getListBoxGenre().setSelectedIndex(problem.genre.getIndex());
		listBoxType.setSelectedIndex(problem.type.getIndex());
		listBoxRandomFlag.setSelectedIndex(problem.randomFlag.getIndex() - 1);
		getTextAreaSentence().setText(problem.getProblemCreationSentence());

		if (problem.answers != null) {
			for (int i = 0; i < problem.answers.length; ++i) {
				String answer = nullToEmpty(problem.answers[i]);
				textBoxAnswer[i].setText(answer);
			}
		}

		if (problem.choices != null) {
			for (int i = 0; i < problem.choices.length; ++i) {
				String choice = nullToEmpty(problem.choices[i]);
				textBoxChoice[i].setText(choice);
			}
		}

		if (problem.creator != null) {
			getTextBoxCreator().setText(problem.creator);
		}

		getLabelAnswerCounter().setText(problem.good + "/" + problem.bad);
		labelVoteCounter.setText(problem.voteGood + "/" + problem.voteBad);

		textBoxNote.setText(problem.note);
		checkBoxImageAnswer.setValue(problem.imageAnswer);
		checkBoxImageChoice.setValue(problem.imageChoice);

		problemId = -1;
		if (!copy) {
			problemId = problem.id;
		}

		if (problemId == -1) {
			labelProblemNumber.setText("新規問題入力中");
			buttonRemovePlayerAnswers.setVisible(false);
		} else {
			labelProblemNumber.setText("問題番号" + problemId + "の問題を修正中");
			buttonRemovePlayerAnswers.setVisible(true);
		}

		if (problem.imageUrl != null) {
			textBoxExternalUrl.setText(problem.imageUrl);
			radioButtonImage.setValue(true);

		} else if (problem.movieUrl != null) {
			textBoxExternalUrl.setText(problem.movieUrl);
			radioButtonYouTube.setValue(true);

		} else {
			radioButtonNone.setValue(true);
		}

		// 問題評価文
		panelProblemFeedback.clear();
		if (problemId != -1) {
			Service.Util.getInstance().getProblemFeedback(problemId, callbackGetProblemFeedback);
		}

		updateForm();
	}

	private final AsyncCallback<List<String>> callbackGetProblemFeedback = new AsyncCallback<List<String>>() {
		public void onSuccess(List<String> result) {
			panelProblemFeedback.add(new HTML(new SafeHtmlBuilder().appendEscapedLines(
					Joiner.on('\n').join(result)).toSafeHtml()));

			if (!result.isEmpty()) {
				panelProblemFeedback.add(buttonClearProblemFeedback);
			}
		}

		public void onFailure(Throwable caught) {
			logger.log(Level.WARNING, "問題フィードバッグの取得に失敗しました", caught);
		}
	};

	private void updateForm() {
		ProblemType type = getSelectedType();

		creationUi.setTypeDescription(type.getDescription());

		for (TextBox textBox : textBoxChoice) {
			textBox.setEnabled(false);
			textBox.setVisible(false);
		}
		for (TextBox textBox : textBoxAnswer) {
			textBox.setEnabled(false);
			textBox.setVisible(false);
		}

		for (int i = 0; i < type.getNumberOfAnswers(); ++i) {
			textBoxAnswer[i].setEnabled(true);
			textBoxAnswer[i].setVisible(true);
		}
		for (int i = type.getNumberOfAnswers(); i < MAX_NUMBER_OF_ANSWERS; ++i) {
			textBoxAnswer[i].setText("");
		}

		for (int i = 0; i < type.getNumberOfChoices(); ++i) {
			textBoxChoice[i].setEnabled(true);
			textBoxChoice[i].setVisible(true);
		}
		for (int i = type.getNumberOfChoices(); i < MAX_NUMBER_OF_CHOICES; ++i) {
			textBoxChoice[i].setText("");
		}

		checkBoxImageAnswer.setVisible(type.isImageAnswer());
		checkBoxImageChoice.setVisible(type.isImageChoice());

		for (Button button : buttonPolygonCreation) {
			button.setVisible(type.isPolygonCreation());
		}

		if (radioButtonNone.getValue()) {
			textBoxExternalUrl.setText("");
			panelExternalUrl.setVisible(false);
		} else {
			panelExternalUrl.setVisible(true);
		}
	}

	public void setEnable(boolean enabled) {
		List<FocusWidget> focusWidgets = new ArrayList<FocusWidget>(
				Arrays.asList(new FocusWidget[] { listBoxGenre, listBoxType, listBoxRandomFlag,
						textAreaSentence, textBoxAnswer[0], textBoxAnswer[1], textBoxAnswer[2],
						textBoxAnswer[3], textBoxChoice[0], textBoxChoice[1], textBoxChoice[2],
						textBoxChoice[3], radioButtonNone, radioButtonImage, radioButtonYouTube,
						textBoxExternalUrl, textBoxCreator, textBoxNote, buttonResetAnswerCount,
						buttonResetVoteCount, checkBoxImageChoice, checkBoxImageAnswer,
						buttonRemovePlayerAnswers, buttonClearProblemFeedback, }));
		focusWidgets.addAll(buttonPolygonCreation);

		for (FocusWidget focusWidget : focusWidgets) {
			focusWidget.setEnabled(enabled);
		}
	}

	/**
	 * 問題を取得する
	 * 
	 * @return 問題
	 */
	public PacketProblem getProblem() {
		PacketProblem problem = new PacketProblem();

		// 問題番号
		problem.id = problemId;

		// ジャンル・出題形式
		problem.genre = ProblemGenre.values()[Integer.parseInt(getListBoxGenre().getValue(
				getListBoxGenre().getSelectedIndex()))];
		problem.type = getSelectedType();
		problem.randomFlag = RandomFlag.values()[listBoxRandomFlag.getSelectedIndex() + 1];

		// 問題文
		problem.setSentence(getTextAreaSentence().getText());

		// 答え
		problem.answers = new String[MAX_NUMBER_OF_ANSWERS];
		for (int i = 0; i < MAX_NUMBER_OF_ANSWERS; ++i) {
			problem.answers[i] = emptyToNull(textBoxAnswer[i].getText().replaceAll("&", "＆"));

			if (problem.type == ProblemType.Typing || problem.type == ProblemType.Effect
					|| problem.type == ProblemType.Flash) {
				problem.answers[i] = StringUtils.convertToFullWidth(problem.answers[i]);
			}
		}

		// 選択肢
		problem.choices = new String[MAX_NUMBER_OF_CHOICES];
		for (int i = 0; i < MAX_NUMBER_OF_CHOICES; ++i) {
			problem.choices[i] = emptyToNull(textBoxChoice[i].getText().replaceAll("&", "＆"));
		}

		if (problem.type == ProblemType.YonTaku || problem.type == ProblemType.Rensou) {
			problem.answers = new String[MAX_NUMBER_OF_ANSWERS];
			problem.answers[0] = problem.choices[0];
		}

		if (radioButtonImage.getValue()) {
			problem.imageUrl = textBoxExternalUrl.getText();
		} else if (radioButtonYouTube.getValue()) {
			problem.movieUrl = textBoxExternalUrl.getText();
		}

		// 作成者
		problem.creator = getTextBoxCreator().getText();

		// 回答数
		String answerCount[] = getLabelAnswerCounter().getText().split("/");
		problem.good = Integer.parseInt(answerCount[0]);
		problem.bad = Integer.parseInt(answerCount[1]);

		// 投票数
		String voteCount[] = labelVoteCounter.getText().split("/");
		problem.voteGood = Integer.parseInt(voteCount[0]);
		problem.voteBad = Integer.parseInt(voteCount[1]);

		// 問題ノート
		problem.note = textBoxNote.getText();

		problem.imageAnswer = isImageAnswer();
		problem.imageChoice = isImageChoice();

		return problem;
	}

	public ProblemType getSelectedType() {
		int selectedIndex = listBoxType.getSelectedIndex();
		String value = listBoxType.getValue(selectedIndex);
		return ProblemType.valueOf(value);
	}

	private void resetAnswerCount() {
		getLabelAnswerCounter().setText("0/0");
	}

	private void resetVoteCount() {
		labelVoteCounter.setText("0/0");
	}

	private void removePlayerAnswer() {
		Service.Util.getInstance().removePlayerAnswers(problemId, callbackRemovePlayerAnswers);
	}

	private final AsyncCallback<Void> callbackRemovePlayerAnswers = new AsyncCallback<Void>() {
		@Override
		public void onSuccess(Void result) {
		}

		@Override
		public void onFailure(Throwable caught) {
			logger.log(Level.WARNING, "誤解答の削除に失敗しました", caught);
		}
	};

	private void clearProblemFeedback() {
		if (problemId == -1) {
			return;
		}

		panelProblemFeedback.clear();
		Service.Util.getInstance().clearProblemFeedback(problemId, callbackClearProblemFeedback);
	}

	private final AsyncCallback<Void> callbackClearProblemFeedback = new AsyncCallback<Void>() {
		@Override
		public void onSuccess(Void result) {
		}

		@Override
		public void onFailure(Throwable caught) {
			logger.log(Level.WARNING, "問題フィードバックの削除に失敗しました", caught);
		}
	};

	public ListBox getListBoxGenre() {
		return listBoxGenre;
	}

	public TextArea getTextAreaSentence() {
		return textAreaSentence;
	}

	public String getTextBoxAnswer(int index) {
		return textBoxAnswer[index].getText();
	}

	public String getTextBoxChoice(int index) {
		return textBoxChoice[index].getText();
	}

	public TextBox getTextBoxCreator() {
		return textBoxCreator;
	}

	private Label getLabelAnswerCounter() {
		return labelAnswerCounter;
	}

	public int getNumberOfChoice() {
		for (int i = 0; i < 4; ++i) {
			if (getTextBoxChoice(i).length() == 0) {
				return i;
			}
		}

		return 4;
	}

	public int getNumberOfAnswers() {
		for (int i = 0; i < 4; ++i) {
			if (getTextBoxAnswer(i).length() == 0) {
				return i;
			}
		}

		return 4;
	}

	public boolean isImageAnswer() {
		ProblemType type = getSelectedType();
		return type.isImageAnswer() && checkBoxImageAnswer.getValue();
	}

	public boolean isImageChoice() {
		ProblemType type = getSelectedType();
		return type.isImageChoice() && checkBoxImageChoice.getValue();
	}

	public int getRandomFlag() {
		return listBoxRandomFlag.getSelectedIndex() + 1;
	}

	public String getImageUrl() {
		if (radioButtonImage.getValue()) {
			return textBoxExternalUrl.getText();
		} else {
			return null;
		}
	}

	public String getYouTubeUrl() {
		if (radioButtonYouTube.getValue()) {
			return textBoxExternalUrl.getText();
		} else {
			return null;
		}
	}

	public void clearProblemId() {
		problemId = -1;
	}

	@Override
	public void onClick(ClickEvent event) {
		Widget sender = (Widget) event.getSource();
		if (sender == buttonResetAnswerCount) {
			resetAnswerCount();
		} else if (sender == buttonResetVoteCount) {
			resetVoteCount();
		} else if (sender == buttonRemovePlayerAnswers) {
			removePlayerAnswer();
		} else if (buttonPolygonCreation.contains(sender)) {
			final int answerIndex = buttonPolygonCreation.indexOf(sender);
			final DialogBoxPolygonCreation polygonCreation = new DialogBoxPolygonCreation(
					getTextBoxChoice(0), getTextBoxAnswer(answerIndex));
			polygonCreation.setPopupPosition(Window.getScrollLeft() + 10,
					Window.getScrollTop() + 10);
			polygonCreation
					.addDialogBoxPolygonCreationListener(new DialogBoxPolygonCreationListener() {
						public void onOk() {
							textBoxAnswer[answerIndex].setText(polygonCreation
									.getPolygonDescription());
						}

						public void onCancel() {
						}
					});
			polygonCreation.show();
		} else if (sender == buttonClearProblemFeedback) {
			clearProblemFeedback();
		} else if (sender == radioButtonNone) {
			updateForm();
		} else if (sender == radioButtonImage) {
			updateForm();
		} else if (sender == radioButtonYouTube) {
			updateForm();
		}
	}

	@Override
	public void onChange(ChangeEvent event) {
		Widget sender = (Widget) event.getSource();
		if (sender == listBoxType) {
			updateForm();
		}
	}
}
